////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNGraphics/inc/WNGLXGraphicsDevice.h"
#include "WNConcurrency/inc/WNSpinMutex.h"
#include "WNPlatform/inc/WNSurface.h"

typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*);
typedef Bool (*glXMakeContextCurrentARBProc)(Display*, GLXDrawable, GLXDrawable, GLXContext);
typedef void (*glXSwapIntervalEXTProc) (Display *dpy, GLXDrawable drawable, int interval);

static glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0;
static glXMakeContextCurrentARBProc glXMakeContextCurrentARB = 0;
static glXSwapIntervalEXTProc glXSwapIntervalEXT = 0;

const WN_SIZE_T WN_RESIZE_RETRY_COUNT = 5;
using namespace WNGraphics;

static WNConcurrency::WNSpinMutex sInitializationCreationLock;

WNGLXGraphicsDevice::WNGLXGraphicsDevice(WNGraphicsResourceFactory* _factory) :
    mResourceFactory(_factory),
    mDisplay(0) {
}

WNGraphicsError WNGLXGraphicsDevice::Initialize(WN_UINT32 _adapter, WN_UINT32 _output) {
    static int visual_attribs[] =
    {
        GLX_X_RENDERABLE    , True,
        GLX_DRAWABLE_TYPE   , GLX_WINDOW_BIT,
        GLX_RENDER_TYPE     , GLX_RGBA_BIT,
        GLX_X_VISUAL_TYPE   , GLX_TRUE_COLOR,
        GLX_RED_SIZE        , 8,
        GLX_GREEN_SIZE      , 8,
        GLX_BLUE_SIZE       , 8,
        GLX_ALPHA_SIZE      , 8,
        GLX_DEPTH_SIZE      , 24,
        GLX_STENCIL_SIZE    , 8,
        GLX_DOUBLEBUFFER    , True,
        GLX_SAMPLE_BUFFERS  , 1,
        GLX_SAMPLES         , 1,
        None
    };

    int context_attribs[] = {
        GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
        GLX_CONTEXT_MINOR_VERSION_ARB, 0,
        None
    };

    mDisplay = XOpenDisplay(0);
    if(!mDisplay) {
        return(eWNWindowCreationError);
    }
    mInitializationState = eDisplayOpened;
    int fbcount = 0;
    GLXFBConfig* fbc = glXChooseFBConfig(mDisplay, DefaultScreen(mDisplay), visual_attribs, &fbcount);
    if(!fbc) {
        return(eWNDisplayModeError);
    }
    mFBConfig = fbc[0];
    XFree(fbc);
    mInitializationState = eFBConfigured;
    sInitializationCreationLock.Lock();
    if(!glXCreateContextAttribsARB) {
        glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc)glXGetProcAddressARB( (const GLubyte *) "glXCreateContextAttribsARB");
        glXMakeContextCurrentARB = (glXMakeContextCurrentARBProc)glXGetProcAddressARB( (const GLubyte *) "glXMakeContextCurrent");
        glXSwapIntervalEXT = (glXSwapIntervalEXTProc)glXGetProcAddressARB( (const GLubyte *) "glXSwapIntervalEXT");
    }
    sInitializationCreationLock.Unlock();
    if ( !(glXCreateContextAttribsARB && glXMakeContextCurrentARB) ) {
        return(eWNWindowCreationError);
    }
    
    if( !(mContext = glXCreateContextAttribsARB(mDisplay, mFBConfig, 0, true, context_attribs) ) ){
        return(eWNWindowCreationError);
    }

    mInitializationState = eContextCreated;

    static int pbuffer_attribs[] = {
            GLX_PBUFFER_WIDTH, 800,
            GLX_PBUFFER_HEIGHT, 600,
            None
    };

    mTempPBuffer = glXCreatePbuffer(mDisplay, mFBConfig, pbuffer_attribs);
    

    if(!glXMakeContextCurrent(mDisplay, mTempPBuffer, mTempPBuffer, mContext) ){
        if(!glXMakeContextCurrent(mDisplay, DefaultRootWindow(mDisplay), DefaultRootWindow(mDisplay), mContext) ) {
            return(eWNMonitorError);
        }
    }
    return(eWNOK);
}

WN_VOID WNGLXGraphicsDevice::Release() {
}


WN_UINT32 WNGLXGraphicsDevice::GetCapability(WNDeviceCaps _cap) {
    switch(_cap) {
        case eWNMultiDrawDevice:
            return(0);
    }
    return(0);
}

WNGraphicsError WNGLXGraphicsDevice::GetSize(WN_UINT32 &_width, WN_UINT32 &_height) {
    return(eWNOK);
}

WNShader* WNGLXGraphicsDevice::CreateShader(WNShaderTypes _type, WN_CHAR* _shaderText) {
    return(WN_NULL);
}

WNBuffer* WNGLXGraphicsDevice::CreateBuffer(WNBufferTypes _type, WN_UINT32 _elementSize, WN_UINT32 _w, WN_UINT32 _h, WN_UINT32 _d) {
    return(WN_NULL);
}

WNTexture* WNGLXGraphicsDevice::CreateTexture(WNTextureTypes _type, WNTextureFormat _format, WN_UINT32 _elementSize, WN_UINT32 _w, WN_UINT32 _h, WN_UINT32 _d) {
    return(WN_NULL);
}

WNRenderTarget* WNGLXGraphicsDevice::CreateRenderTarget(WNTexture* _texture) {
    return(WN_NULL);
}

WNDrawList* WNGLXGraphicsDevice::CreateDrawList() {
    return(WN_NULL);
}

WNGraphicsError WNGLXGraphicsDevice::SetDrawList(WNDrawList* _list) {
    return(eWNOK);
}

WNGraphicsError WNGLXGraphicsDevice::StartDraw() {
    if(!mActiveSurface) {
        if(!glXMakeContextCurrent(mDisplay, mTempPBuffer, mTempPBuffer, mContext) ){
            if(!glXMakeContextCurrent(mDisplay, DefaultRootWindow(mDisplay), DefaultRootWindow(mDisplay), mContext) ) {
                return(eWNMonitorError);
            }
        }
    } else {
        if(!glXMakeContextCurrent(mDisplay, mActiveSurface->GetNativeGraphicsHandle(), mActiveSurface->GetNativeGraphicsHandle(), mContext)) {
            return(eWNBadSurface);
        }
    }
    return(eWNOK);
}

WNGraphicsError WNGLXGraphicsDevice::EndDraw() {
    if(!mActiveSurface) {
        return(eWNNoSurface);
    }
    
    glXSwapBuffers ( mDisplay, mActiveSurface->GetNativeGraphicsHandle() );
    glFinish();
    return(eWNOK);
}

WNGraphicsError WNGLXGraphicsDevice::SubmitDrawList(WNDrawList* _list) {
    return(eWNOK);
}

WNGraphicsError WNGLXGraphicsDevice::BindShader(WNShader* _resource) {
    return(eWNOK);
}

WNGraphicsError WNGLXGraphicsDevice::BindBuffer(WNBuffer* _resource, WN_UINT32 _location) {
    return(eWNOK);
}

WNGraphicsError WNGLXGraphicsDevice::BindTexture(WNTexture* _texture, WN_UINT32 _location) {
    return(eWNOK);
}

WNGraphicsError WNGLXGraphicsDevice::BindRenderTarget(WNRenderTarget* _texture, WN_UINT32 _location) {
    return(eWNOK);
}

WNGraphicsError WNGLXGraphicsDevice::Clear() {
    glClear ( GL_COLOR_BUFFER_BIT );
    return(eWNOK);
}

WNGraphicsError WNGLXGraphicsDevice::SetActiveSurface(WNContainers::WNResourcePointer<WNPlatform::WNSurface> _surface) {
    mActiveSurface = _surface;
    return(eWNOK);
}

WN_VOID WNGLXGraphicsDevice::SetClearColor(WN_FLOAT32* _color) {
    glClearColor ( _color[0], _color[1], _color[2], _color[3] );
}

WNGraphicsError WNGLXGraphicsDevice::BindSurface(WNContainers::WNResourcePointer<WNPlatform::WNSurface>& _surface, WN_BOOL _sync) {
    WNGLXGraphicsData * data = WN_NEW(WNGLXGraphicsData());
    XSync(mDisplay, false);
    data->mSync = _sync;
    if(data->mSync && glXSwapIntervalEXT) {
        unsigned int maxSwap;
        glXQueryDrawable(mDisplay, _surface->GetNativeGraphicsHandle(), GLX_MAX_SWAP_INTERVAL_EXT, &maxSwap);
        glXSwapIntervalEXT(mDisplay, _surface->GetNativeGraphicsHandle(), maxSwap);
    }
    _surface->RegisterData(WNPlatform::WNSurface::eRDTGraphics, data);

    return(eWNOK);
}
